#include "ScriptProcessor.h"

#include <QtXml>
#include <QtScript>
#include <QtCore>

#ifdef _MSC_VER
   typedef signed __int8     int8_t;
   typedef signed __int16    int16_t;
   typedef signed __int32    int32_t;
   typedef unsigned __int8   uint8_t;
   typedef unsigned __int16  uint16_t;
   typedef unsigned __int32  uint32_t;
   typedef signed __int64       int64_t;
   typedef unsigned __int64     uint64_t;
#endif

//////////////////////
// ScriptContext class

ScriptContext::ScriptContext()
{
}


///////////////////////////
// ScriptDataIterator class

typedef struct {
    uint16_t type;
    uint16_t thread;
    uint32_t timestamp;
    uint32_t arg1;
    uint32_t arg2;
} InstrumentRecord;

inline unsigned short swapU16(unsigned short value)
{
    return ((value >> 8) | (value << 8));
}

inline unsigned long swapU32(unsigned long value)
{
    return ((swapU16(value) << 16) | swapU16(value >> 16));
}

ScriptDataIterator::ScriptDataIterator(ScriptContext* context)
: context(context)
{
}
    
void ScriptDataIterator::setUp(const QDomNode& node)
{
    activeEvent = -1;
    
    QFile file;
    file.setFileName(context->bufferFilePath);
    file.open(QIODevice::ReadOnly);
    data = file.readAll();
    file.close();
    
    totalEvents = data.size() / sizeof(InstrumentRecord);
    context->iteratorTotal = totalEvents;
}

bool ScriptDataIterator::hasNext()
{
    return (activeEvent < totalEvents);
}

void ScriptDataIterator::next()
{
    activeEvent++;
    context->iteratorIndex = activeEvent;
    if (activeEvent % 10000 == 0)
        qDebug() << "now at " << activeEvent;
}

QScriptValue ScriptDataIterator::at(int index, QScriptEngine* engine)
{
    InstrumentRecord *events = (InstrumentRecord *)data.data();
    
    InstrumentRecord event;
    if (context->swapBytes) {
        // swap all short values in the struct
        event.type = swapU16(events[index].type);
        event.thread = swapU16(events[index].thread);
        
        // swap all long vaues in the struct
        event.timestamp = swapU32(events[index].timestamp);
        event.arg1 = swapU32(events[index].arg1);
        event.arg2 = swapU32(events[index].arg2);
    }
    
    QScriptValue eventObject = engine->newObject();
    eventObject.setProperty("_number", QScriptValue(engine, index));
    eventObject.setProperty("_type", QScriptValue(engine, event.type));
    eventObject.setProperty("_thread", QScriptValue(engine, event.thread));
    eventObject.setProperty("_timestamp", QScriptValue(engine, (unsigned int)event.timestamp));
    eventObject.setProperty("_arg1", QScriptValue(engine, (unsigned int)event.arg1));
    eventObject.setProperty("_arg2", QScriptValue(engine, (unsigned int)event.arg2));

    return eventObject;
}

QScriptValue ScriptDataIterator::value(QScriptEngine* engine)
{
    return at(activeEvent, engine);
}


//////////////////////////
// ScriptDataBinding class

ScriptDataBinding::ScriptDataBinding()
{
}

ScriptDataBinding::ScriptDataBinding(ScriptContext* context)
: context(context)
{
}

ScriptDataBinding::ScriptDataBinding(const ScriptDataBinding& value)
: name(value.name), context(value.context), type(value.type)
{
}

ScriptDataBinding& ScriptDataBinding::operator=(const ScriptDataBinding& value)
{
    context = value.context;
    type = value.type;
    name = value.name;
    return *this;
}

void ScriptDataBinding::setUp(const QDomNode& node)
{
    QString typeString = node.namedItem("type").firstChild().toText().data();
    if (typeString == "iterator")
        type = iterator;
    else if (typeString == "stateless")
        type = stateless;
    else if (typeString == "statefull")
        type = statefull;
    
    name = node.namedItem("name").firstChild().toText().data();
}

void ScriptDataBinding::print() const
{
    qDebug() << type << " --- " << name;
}

void ScriptDataBinding::preProcess(QScriptEngine* matchEngine, QScriptEngine* actionEngine)
{
    if (type == iterator) {
//        matchEngine->globalObject().setProperty(name, context->matcherIteratorValue);
        actionEngine->globalObject().setProperty(name, context->actionIteratorValue);
    }
    else if (type == stateless || type == statefull) {
        actionEngine->globalObject().setProperty(name, actionEngine->newObject());
    }
}

void ScriptDataBinding::postProcess(QScriptEngine* matchEngine, QScriptEngine* actionEngine)
{
    if (type == stateless) {
        QScriptValueIterator it(actionEngine->globalObject().property(name));
        QMap<QString, QString> values;
        while (it.hasNext()) {
            it.next();
    //        qDebug() << name << "." << it.name() << " = " << it.value().toString();
            values[it.name()] = it.value().toString();
        }
        
        if (values.size() > 0)
            context->dataSource->addRow(name, values);
    }
    else if (type == statefull) {
        QScriptValueIterator it(actionEngine->globalObject().property(name));
        QMap<QString, QString> values;
        while (it.hasNext()) {
            it.next();
            values[it.name()] = it.value().toString();
        }
        
        if (values.size() > 0)
            context->dataSource->addUpdateRow(name, values);
    }
}

void ScriptDataBinding::createBindingPropertiesForHardCompile(QScriptEngine* actionEngine)
{
    if (type == iterator) {
        actionEngine->globalObject().setProperty(name, actionEngine->newObject());
    }
    else if (type == stateless) {
        actionEngine->globalObject().setProperty(name, actionEngine->newObject());
    }
}


///////////////////
// ScriptRule class

ScriptRule::ScriptRule(QString matcher, QString action)
: matcher(matcher), action(action)
{
}

ScriptRule::ScriptRule(const ScriptRule& value)
: matcher(value.matcher), action(value.action)
{
}

ScriptRule& ScriptRule::operator=(const ScriptRule& value)
{
    matcher = value.matcher;
    action = value.action;
    return *this;
}

void ScriptRule::setUp(const QDomNode& node)
{
    QDomNode matcherNode = node.namedItem("matcher").firstChild();
    QDomNode actionNode = node.namedItem("action").firstChild();
    if (matcherNode.isNull())
        matcher = "1";
    else
        matcher = matcherNode.toCDATASection().data();
    
    if (actionNode.isNull())
        action = "";
    else
        action = actionNode.toCDATASection().data();
}

void ScriptRule::print() const
{
    qDebug() << matcher << "-->" << action;
}
    
void ScriptRule::evaluate(QScriptEngine* matchEngine, QScriptEngine* actionEngine)
{
    if (matchEngine->evaluate(matcher).toBoolean())
        actionEngine->evaluate(action);
}

void ScriptRule::execute(QScriptEngine* actionEngine)
{
    actionEngine->evaluate(action);
}


////////////////////////
// ScriptProcessor class

ScriptProcessor::ScriptProcessor(ScriptContext* context)
: context(context), iterator(context)
{
}

void ScriptProcessor::loadScript(const QString & path)
{
    QFile scriptFile(path);
    if (!scriptFile.open(QIODevice::ReadOnly | QIODevice::Text))
        return;
    
    rules.clear();
    
    QDomDocument scriptXml;
    scriptXml.setContent(&scriptFile);
    
    QDomNodeList rulesNodes = scriptXml.elementsByTagName("rules");
    if (rulesNodes.size() > 0) {
        QDomNode rulesNode = rulesNodes.at(0);
        for (int index = 0; index < rulesNode.childNodes().size(); index++) {
            ScriptRule rule;
            rule.setUp(rulesNode.childNodes().at(index));
            rules.append(rule);
        }
    }
    
    QDomNodeList bindingsNodes = scriptXml.elementsByTagName("bindings");
    if (bindingsNodes.size() > 0) {
        QDomNode bindingsNode = bindingsNodes.at(0);
        for (int index = 0; index < bindingsNode.childNodes().size(); index++) {
            ScriptDataBinding binding(context);
            binding.setUp(bindingsNode.childNodes().at(index));
            bindings.append(binding);
        }
    }
    
    QDomNodeList iteratorNodes = scriptXml.elementsByTagName("iterator");
    if (iteratorNodes.size() > 0)
        iterator.setUp(iteratorNodes.at(0));
    
    scriptFile.close();
    
    hardCompileRules();
}

void ScriptProcessor::printRules() const
{
    for (int index = 0; index < rules.size(); index++)
        rules.at(index).print();
}

void ScriptProcessor::printBindings() const
{
    for (int index = 0; index < bindings.size(); index++)
        bindings.at(index).print();
}

void ScriptProcessor::print() const
{
    qDebug() << "Bindings:";
    printBindings();
    qDebug() << "Rules:";
    printRules();
}

void ScriptProcessor::compileRules()
{
    for (int ruleIndex = 0; ruleIndex < rules.size(); ruleIndex++)
        compiledRule.append(QString("__matcherResults[%1] = (%2 ? 1 : 0);").arg(ruleIndex).arg(rules.at(ruleIndex).matcher));
}

void ScriptProcessor::hardCompileRules()
{
    compiledRule = QString("var __event = 0;for (__event=0;__event<%1;__event=__event+1) {").arg(context->iteratorTotal);
    
    compiledRule.append("__getEventData(__event);");
    
    for (int ruleIndex = 0; ruleIndex < rules.size(); ruleIndex++)
        if (rules.at(ruleIndex).action.size() > 0)
            compiledRule.append(QString("if (%1) {%2}").arg(rules.at(ruleIndex).matcher, rules.at(ruleIndex).action));
    
    compiledRule.append("__setEventData(__event);");
    
    compiledRule.append("}");
    
//    qDebug() << compiledRule;
}

void ScriptProcessor::preProcessBindings(QScriptEngine* matchEngine, QScriptEngine* actionEngine)
{
    for (int index = 0; index < bindings.size(); index++) {
        ScriptDataBinding binding = bindings.at(index);
        binding.preProcess(matchEngine, actionEngine);
    }
}

void ScriptProcessor::postProcessBindings(QScriptEngine* matchEngine, QScriptEngine* actionEngine)
{
    for (int index = 0; index < bindings.size(); index++) {
        ScriptDataBinding binding = bindings.at(index);
        binding.postProcess(matchEngine, actionEngine);
    }
}

//void ScriptProcessor::processCompiledRules()
//{
//    QScriptEngine matchEngine, actionEngine;
//    
//    while (iterator.hasNext()) {
//        iterator.next();
//        context->matcherIteratorValue = iterator.value(&matchEngine);
//        context->actionIteratorValue = iterator.value(&actionEngine);
//        
//        // Create and register an array to receive the matcher results
//        QScriptValue matcherResultsArray = matchEngine.newArray(rules.size());
//        matchEngine.globalObject().setProperty("__matcherResults", matcherResultsArray);
//        
//        // Setup the bindings for matching
//        preProcessBindings(&matchEngine, &actionEngine);
//        
//        // Evaluate the compiled match rules
//        matchEngine.evaluate(compiledRule);
//        
//        // Get the matcher results
//        matcherResultsArray = matchEngine.globalObject().property("__matcherResults");
//        
//        // Iterate over the results and evaluate the matching actions
//        QStringList matcherResultsStringList;
//        matcherResultsStringList = qscriptvalue_cast(matcherResultsArray, &matcherResultsStringList);
//        for (int matcherResultIndex = 0; matcherResultIndex < matcherResultsStringList.size(); matcherResultIndex++) {
//            if (matcherResultsStringList.at(matcherResultIndex) == "1") {
//                preProcessBindings(&matchEngine, &actionEngine);
//                ScriptRule rule = rules.at(matcherResultIndex);
//                rule.execute(&actionEngine);
//                postProcessBindings(&matchEngine, &actionEngine);
//            }
//        }
//    }
//}

static ScriptContext* eventDataContext;

static QScriptValue getEventData(QScriptContext *ctx, QScriptEngine *eng)
{
    int index = ctx->argument(0).toInt32();
    eventDataContext->actionIteratorValue = eventDataContext->dataIterator->at(index, eng);
    eventDataContext->processor->preProcessBindings(eng, eng);
    return eng->nullValue();
}

static QScriptValue setEventData(QScriptContext *ctx, QScriptEngine *eng)
{
    int index = ctx->argument(0).toInt32();
    eventDataContext->processor->postProcessBindings(eng, eng);
    if (index % 10000 == 0)
        qDebug() << "Processed " << index << " events";
    return eng->nullValue();
}

static QScriptValue getSymbol(QScriptContext *ctx, QScriptEngine *eng)
{
    int pointer = ctx->argument(0).toInt32();
    QString symbol = (*eventDataContext->elfReader)[pointer];
    return QScriptValue(eng, symbol);
}

static QScriptValue getValue(QScriptContext *ctx, QScriptEngine *eng)
{
    // _lastValue(name, keyColumn, keyValue, ...)
    QString name = ctx->argument(0).toString();
    QMap<QString, QString> constraints;
    for (int key = 1; key < ctx->argumentCount(); key += 2)
        constraints[ctx->argument(key).toString()] = ctx->argument(key + 1).toString();
    QMap<QString, QString> dbValue = eventDataContext->dataSource->getRow(name, constraints);
    
    QScriptValue value = eng->newObject();
    QMapIterator<QString, QString> dbValueIterator(dbValue);
    while (dbValueIterator.hasNext()) {
        dbValueIterator.next();
        value.setProperty(dbValueIterator.key(), QScriptValue(eng, dbValueIterator.value()));
    }
    
    return value;
}

void ScriptProcessor::processHardCompiledRules()
{
    ELFReader elfReader;
    elfReader.load(context->elfFilePath);
    
    context->processor = this;
    context->dataIterator = &iterator;
    context->elfReader = &elfReader;
    eventDataContext = context;
    
    QScriptEngine matchEngine, actionEngine;
    
    // Register the interaction methods
    actionEngine.globalObject().setProperty("__getEventData", actionEngine.newFunction(getEventData));
    actionEngine.globalObject().setProperty("__setEventData", actionEngine.newFunction(setEventData));
    actionEngine.globalObject().setProperty("_symbol", actionEngine.newFunction(getSymbol));
    actionEngine.globalObject().setProperty("_lastValue", actionEngine.newFunction(getValue));
    
    // Evaluate the compiled match rules
    actionEngine.evaluate(compiledRule);
}

void ScriptProcessor::process()
{
    QTime processingCounter;
    processingCounter.start();
//    processCompiledRules();
    processHardCompiledRules();
    qDebug() << QString("ScriptProcessor::process %1 msec").arg(processingCounter.elapsed());
}
